#!/usr/bin/ruby

#######################################
#        MFprio.rb                                                                             
#        description;                                                                          
#            provide packet prioritization                                            
#        version           1.2                                                                 
#        last modified     06/23/06
#       Last change, updated error reporting, and corrected
#       the lack of checking prior to starting prio
#######################################

INT_VERSION=1.2
def prio_ver
   return "\tINTERFACE library version: #{INT_VERSION}\n"
end

def add_Pint(zone)
    retary=Array.new
    if !test(?r, "/jail/acl/zones/#{zone}/interface")  
        return "\tPRIO FAILED: zone #{zone} is invalid\n"
    elsif !test(?r,"/jail/acl/zones/#{zone}/interface/int")
        return "\tPRIO FAILED: zone #{zone} interface is invalid\n"    
    elsif !active?("/jail/acl/zones/#{zone}") || test(?r, "/jail/acl/zones/#{zone}/interface/prio")
        myprio=MFreadlines("/jail/acl/zones/#{zone}/interface/prio")
        if !myprio || myprio.length < 1
            return "\tPRIO FAILED: zone #{zone}, prio is invalid\n"
        end
        # ok we should be good lets get to adding (format should be tcp:port || udp port, the order they are in the file is the order of prioritization)
        bands=myprio.length+1
        if bands < 3
            bands = 3
        end
        int=MFreadlines("/jail/acl/zones/#{zone}/interface/int").shift
        if !system("#{$tc} qdisc add dev #{int} root handle 1: prio bands #{bands}")
            return "PRIO FAILED: couldn't add root handle for zone #{zone}\n"
        end
        pcnt=0
        myprio.each {|r|
            if r.include?(" ")
                r=r.gsub(" ","")
            end
            t=r.split(':')
            pcnt = pcnt + 1
            hid=pcnt*10          
            if !system("#{$tc} qdisc add dev #{int} parent 1:#{pcnt} handle #{hid}: sfq")
                        retary << "\tPRIO FAILED: adding handle for line #{r}\n"
            else
                if t[0].downcase == "udp" && t[1]
                    if !system("#{$tc} filter add dev #{int} protocol ip parent 1: prio #{pcnt} u32 match ip protocol 17 0xff match ip dport #{t[1]} 0xffff flowid 1:#{pcnt}")
                        retary << "\tPRIO FAILED: adding filter for line #{r}\n"
                    end
                    if !system("#{$tc} filter add dev #{int} protocol ip parent 1: prio #{pcnt} u32 match ip protocol 17 0xff match ip sport #{t[1]} 0xffff flowid 1:#{pcnt}")
                        retary << "\tPRIO FAILED: adding filter for line #{r}\n"
                    end
                elsif t[0].downcase == "tcp" && t[1]
                    if !system("#{$tc} filter add dev #{int} protocol ip parent 1: prio #{pcnt} u32 match ip protocol 6 0xff match ip dport #{t[1]} 0xffff flowid 1:#{pcnt}")
                        retary << "\tPRIO FAILED: adding filter for line #{r}\n"
                    end
                    if !system("#{$tc} filter add dev #{int} protocol ip parent 1: prio #{pcnt} u32 match ip protocol 6 0xff match ip sport #{t[1]} 0xffff flowid 1:#{pcnt}")
                        retary << "\tPRIO FAILED: adding filter for line #{r}\n"
                    end
                else
                    # neither tcp nor udp, see if we have to parts, if so, assume numeric 0xff match for protcol and use dport/sport
                    if t[1]
                        if !system("#{$tc} filter add dev #{int} protocol ip parent 1: prio #{pcnt} u32 match ip protocol #{t[0]} 0xff match ip dport #{t[1]} 0xffff flowid 1:#{pcnt}")
                            retary << "\tPRIO FAILED: adding filter for line #{r}\n"
                        end
                        if !system("#{$tc} filter add dev #{int} protocol ip parent 1: prio #{pcnt} u32 match ip protocol #{t[0]} 0xff match ip sport #{t[1]} 0xffff flowid 1:#{pcnt}")
                            retary << "\tPRIO FAILED: adding filter for line #{r}\n"
                        end
                    else
                        # else, just match on proto
                        if !system("#{$tc} filter add dev #{int} protocol ip parent 1: prio #{pcnt} u32 match ip protocol #{t[0]} 0xff flowid 1:#{pcnt}")
                            retary << "\tPRIO FAILED: adding filter for line #{r}\n"
                        end
                    end
                end
            end
        }
        return retary.to_s
    else
        return "\tPRIO FAILED: zone #{zone}, prio is invalid\n"
    end        
end

def drop_Pint(zone)
    int=MFreadlines("#{zone}/int").shift
    if !system("#{$tc} qdisc del dev #{int} root")
        return "\tFAILED: removing prioritization for #{int}\n"
    end
end

def Pint_start(tzone="all")
    retary=Array.new
    if tzone == "all"
        dir=Dir.new("/jail/acl/zones/")
            dir.each {|zone|
            next if zone.include?("\.")
            next if ! test(?r, "/jail/acl/zones/#{zone}/interface")
            if active?("/jail/acl/zones/#{zone}") && test(?r, "/jail/acl/zones/#{zone}/interface/prio")
                retary << "\tActivating  Prioritization for zone #{zone}\n"
                retary << add_Pint(zone)
            else
                if tzone != "all"
                    retary << "\tZONE #{zone} is deactivated by config or lacks a prio file.. skipping\n"
                end
            end
            }
    else
    if active?("/jail/acl/zones/#{tzone}")  && test(?r, "/jail/acl/zones/#{tzone}/interface/prio")
                retary << "\tActivating interface  Prioritization for zone #{tzone}\n"
                retary << add_Pint(tzone)
            else
                retary << "\tZONE #{tzone} is deactivated by config.. skipping\n"
            end   
    end
    return(retary.to_s)
end

def Pint_stop(tzone="all")
    retary=Array.new
    if tzone == "all"
        # ok we will cycle through each zone, find the info and go to dang work
        dir=Dir.new("/jail/acl/zones/")
            dir.each {|zone|
            next if zone.include?("\.")
            next if ! test(?r, "/jail/acl/zones/#{zone}/interface")
            next if ! test(?r, "/jail/acl/zones/#{zone}/interface/prio")
            retary << "\tDeactivating prioritization on interface for zone #{zone}\n"
            retary << drop_Pint("/jail/acl/zones/#{zone}/interface")
            }
    else
        if test(?r, "/jail/acl/zones/#{tzone}/interface") && test(?r, "/jail/acl/zones/#{tzone}/interface/prio")
            retary << "\tDeactivating prioritization on interface for zone #{tzone}\n"
            retary << drop_Pint("/jail/acl/zones/#{tzone}/interface")
        end
    end
    return(retary.to_s)
end

def Pint_restart(zone)
    retary=Array.new
    retary << Pint_stop(zone)
    retary << Pint_start(zone)
    return (retary.to_s)
end